﻿using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity;

/// <summary>
/// Creates a new instance of a persistence store for the specified user type.
/// </summary>
/// <typeparam name="TUser">The type representing a user.</typeparam>
public class UserStore : UserStore<User, Role, UserClaim, UserRole, UserLogin, UserToken, RoleClaim, Group, UserGroup, Level, DbContext, string>
{
    /// <summary>
    /// Constructs a new instance of <see cref="UserStore{TUser}"/>.
    /// </summary>
    /// <param name="context">The <see cref="DbContext"/>.</param>
    /// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
    public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
}

/// <summary>
/// Represents a new instance of a persistence store for the specified user and role types.
/// </summary>
/// <typeparam name="TUser">The type representing a user.</typeparam>
/// <typeparam name="TRole">The type representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
public class UserStore<TUser, TRole, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TGroup, TUserGroup, TLevel, TContext, TKey> :
    UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>,
    IIdentityStore<TUser, TKey>,
    IUserGroupStore<TUser, TKey>,
    ILevelStore<TUser, TKey>
    where TKey : IEquatable<TKey>, IConvertible
    where TContext : DbContext
    where TRole : Role<TKey>, new()
    where TUser : User<TKey>, new()
    where TGroup : Group<TKey>, new()
    where TUserGroup : UserGroup<TKey>, new()
    where TUserClaim : UserClaim<TKey>, new()
    where TUserRole : UserRole<TKey>, new()
    where TUserLogin : UserLogin<TKey>, new()
    where TUserToken : UserToken<TKey>, new()
    where TRoleClaim : RoleClaim<TKey>, new()
    where TLevel : Level<TKey>, new()
{
    /// <summary>
    /// Constructs a new instance of <see cref="UserStore{TUser, TRole, TContext, TKey}"/>.
    /// </summary>
    /// <param name="context">The <see cref="DbContext"/>.</param>
    /// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
    public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }

    protected DbSet<TGroup> Groups => Context.Set<TGroup>();
    protected DbSet<TUserGroup> UserGroups => Context.Set<TUserGroup>();
    protected DbSet<TLevel> Levels => Context.Set<TLevel>();

    #region implement from IIdentityStore
    /// <summary>
    /// 使用 id 获取当前用户
    /// </summary>
    /// <param name="id"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task<TUser> FindByIdAsync(TKey id, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        return Context.Set<TUser>().FindAsync(new object[] { id }, cancellationToken).AsTask();
    }
    /// <summary>
    /// 修改名字
    /// </summary>
    /// <param name="identity"></param>
    /// <param name="name"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetName(TUser user, string name, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Name = name;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改昵称
    /// </summary>
    /// <param name="identity"></param>
    /// <param name="alias"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetAlias(TUser user, string alias, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Alias = alias;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改头像
    /// </summary>
    /// <param name="identity"></param>
    /// <param name="image"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetImage(TUser user, string image, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Image = image;
        return Task.CompletedTask;
    }
    #endregion

    #region implement from IUserGroupStore
    /// <summary>
    /// 用户添加到组织(组)
    /// </summary>
    /// <param name="user"></param>
    /// <param name="groupName"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> AddToGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user == null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (groupName.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(groupName));
        }

        var group = await Groups.FirstOrDefaultAsync(x => x.Name == groupName, cancellationToken);
        if (group == null)
        {
            return IdentityResult.Failed(ErrorDescriber.InvalidGroupName(groupName));
        }

        if (await UserGroups.AnyAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(group.Id), cancellationToken))
        {
            return IdentityResult.Failed(ErrorDescriber.UserAlreadyInGroup(user.Name, groupName));
        }

        try
        {
            await UserGroups.AddAsync(new TUserGroup { UserId = user.Id, GroupId = group.Id }, cancellationToken);
            await SaveChanges(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 从组织中移除用户
    /// </summary>
    /// <param name="user"></param>
    /// <param name="groupName"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> RemoveFromGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user == null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (groupName.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(groupName));
        }

        var group = await Groups.FirstOrDefaultAsync(x => x.Name == groupName, cancellationToken);
        if (group == null)
        {
            return IdentityResult.Failed(ErrorDescriber.InvalidGroupName(groupName));
        }

        var userGroup = await UserGroups.FirstOrDefaultAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(group.Id), cancellationToken);
        if (userGroup == null)
        {
            return IdentityResult.Failed(ErrorDescriber.UserNotInGroup(user.Name, groupName));
        }

        try
        {
            UserGroups.Remove(userGroup);
            await SaveChanges(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 获取组织中所有用户
    /// </summary>
    /// <param name="groupName"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IList<TUser>> GetGroupUsersAsync(string groupName, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (groupName.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(groupName));
        }

        var query = from _userGroup in UserGroups
                    join _group in Groups on _userGroup.GroupId equals _group.Id
                    join _user in Users on _userGroup.UserId equals _user.Id
                    where _group.Name == groupName
                    select _user;
        return await query.ToListAsync(cancellationToken);
    }
    /// <summary>
    /// 获取用户加入的所有组织名称
    /// </summary>
    /// <param name="user"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IList<string>> GetUserGroupsAsync(TUser user, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user == null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        var query = from _userGroup in UserGroups
                    join _group in Groups on _userGroup.GroupId equals _group.Id
                    where _userGroup.UserId.Equals(user.Id)
                    select _group.Name;
        return await query.ToListAsync(cancellationToken);
    }
    /// <summary>
    /// 用户是否在组织中
    /// </summary>
    /// <param name="user"></param>
    /// <param name="groupName"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<bool> IsInGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user == null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (groupName.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(groupName));
        }

        var query = from _userGroup in UserGroups
                    join _group in Groups on _userGroup.GroupId equals _group.Id
                    where _userGroup.UserId.Equals(user.Id) && _group.Name == groupName
                    select _group.Id;
        return await query.AnyAsync(cancellationToken);
    }
    #endregion

    /// <summary>
    /// 设置用户级别
    /// </summary>
    /// <param name="user"></param>
    /// <param name="level"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task SetLevel(TUser user, string level, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        var existLevel = await Levels.FirstOrDefaultAsync(x => x.Name == level, cancellationToken);
        if (existLevel == null)
        {
            throw new ArgumentNullException(nameof(level));
        }

        user.LevelId = existLevel.Id;
    }
}